//	Google Desktop Search Opera History Component
//	Copyright (C) 2005 Doug Bromley
//	Version 0.4 Beta
//
//	This program is free software; you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation; either version 2 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program; if not, write to the Free Software
//	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//	http://www.gnu.org/copyleft/gpl.html
using System;
using System.IO;
using System.Diagnostics;
using System.Threading;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using System.Windows.Forms;
using System.Text;
using System.Text.RegularExpressions;

using GoogleDesktopAPILib;

namespace OperaHistoryComponent
{
	/// <summary>
	/// This component retrieves parses and sends various data to the Google Desktop Search application.
	/// </summary>
	[ Guid("1a3a840f-822d-4c90-43e3-ee3901f3f1d2") ]
	public class OperaHistoryComponent
	{
		internal struct operaHistoryData
		{
			public DateTime accessTime;
			public string pageTitle;
			public string pageURL;
			public string pageContent;
		}
		internal struct operaBookmarkData
		{
			public DateTime accessTime;
			public string title;
			public string url;
			public string content;
			public bool booked;
		}

		const string comName = "Opera History Component";
		const string guid = "{1a3a840f-822d-4c90-43e3-ee3901f3f1d2}";
		const string kWindowsRunRegKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Run";
		string histFilePath;
		string bookFilePath;
		string histFile;
		string bookFile;
		int counter;
		DateTime histLastIndexed;
		DateTime bookLastIndexed;
		
		public OperaHistoryComponent()
		{
			
		}

		public static void Main(string [] args) 
		{
			OperaHistoryComponent ohc = new OperaHistoryComponent();
			bool active = false;

			if(args.Length == 0)
			{
				active = true;
				// Find history file
				try
				{
					//Check if the history file location is already set in registry.
					//This is the much requested update that enables people to change the location of the history file.
					RegistryKey histFileKey = Registry.CurrentUser.OpenSubKey("Software\\Opera History Component", true);
					ohc.histFile = histFileKey.GetValue("HistoryLocation").ToString();
					RegistryKey bookFileKey = Registry.CurrentUser.OpenSubKey("Software\\Opera History Component", true);
					ohc.bookFile = bookFileKey.GetValue("BookmarksLocation").ToString();
					if(ohc.histFile.Length > 1)
					{
						ohc.histFilePath = histFileKey.GetValue("HistoryLocation").ToString();
						ohc.bookFilePath = bookFileKey.GetValue("BookmarksLocation").ToString();
					}
					else
					{
						if(File.Exists(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Opera\\Opera\\profile\\global.dat"))
						{
							ohc.histFilePath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Opera\\Opera\\profile\\global.dat";
							ohc.bookFilePath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Opera\\Opera\\profile\\opera6.adr";
						}
						else
						{
							ohc.histFilePath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Opera\\profile\\global.dat";
							ohc.bookFilePath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Opera\\profile\\opera6.adr";
						}
					}
				}
				catch(Exception e)
				{
					ohc.SimpleMessageBox(comName, "Problem getting environment variables to find your Opera history file! - " + e.ToString(), false);	    
				}
			}
			else
			{
				switch (args[0])
				{
					case "-register":
						if (ohc.DoRegistration(true) == false)
							return;
						active = true;
						break;
					case "-unregister":
						// Unregister
						ohc.DoRegistration(false);
						active = false;
						break;
					default:
						active = true;
						break;
				}
			}

			if(active == false)
			{
				// Kill existing OHC processes
				Process[] myProcesses;
				myProcesses = Process.GetProcessesByName("OperaHistoryComponent");
				foreach(Process myProcess in myProcesses)
				{						
					myProcess.Kill();
				}
				Application.Exit();
			}
			else if(active == true)
			{
				ohc.ExtractHistory();
			}
			else
			{
				//Put here to stop the headache.
				Process.GetCurrentProcess().Kill();
			}			
		}


		/// <summary>
		/// Performs the registration of the component with the GDS application.
		/// Some code borrowed from Google MSN Example
		/// </summary>
		/// <param name="install">Is the component being installed?</param>
		/// <returns></returns>
		private bool DoRegistration(bool install) 
		{
			try 
			{
				// register/unregister the component
				GoogleDesktopIndexingComponentRegisterClass gdsReg = new GoogleDesktopIndexingComponentRegisterClass();

				if (install) 
				{          
					try 
					{
						// register with GDS
						object [] componentDesc = new object[6] {"Title", "Opera Component", "Description", "Captures and indexes the Opera 8 history and bookmarks(new)", "Icon", "no icon"};
						gdsReg.RegisterIndexingComponent(guid,componentDesc);

						// add the program to Run
						RegistryKey key = Registry.CurrentUser.OpenSubKey(kWindowsRunRegKey, true);
						key.SetValue(comName, "\""+Directory.GetCurrentDirectory()+ "\\" + Process.GetCurrentProcess().ProcessName + ".exe\"");
						key.Close();

						//Init the settings of the component
						RegistryKey appSettings = Registry.CurrentUser.CreateSubKey("Software\\Opera History Component");
						appSettings.SetValue("HistoryLocation", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Opera\\Opera\\profile\\global.dat");
						appSettings.SetValue("BookmarksLocation", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Opera\\Opera\\profile\\opera6.adr");

						histFilePath = appSettings.GetValue("HistoryLocation").ToString();
						bookFilePath = appSettings.GetValue("BookmarksLocation").ToString();

						SimpleMessageBox(comName, "Registration completed successfully!", false);
					}
					catch(COMException e)
					{
						// check if not already registered
						if ((UInt32)e.ErrorCode != 0x80040006)       // E_COMPONENT_ALREADY_REGISTERED
						{
							SimpleMessageBox("COM Exception", "A registration exception has occured - The component is already registered with GDS.  Please make sure you uninstall all previous versions of the component: " + e.ToString(), false);
							return false;
						}
						else
							SimpleMessageBox(comName, "Error in registration process.  Its possible the component is already registered.  If so then please ensure all previous versions are properly uninstalled.!", false);
					}
				}
				else
				{
					try 
					{
						gdsReg.UnregisterIndexingComponent(guid);
					}
					catch(COMException e)
					{
						// check if not already unregistered
						if ((UInt32)e.ErrorCode != 0x80040002)        // E_COMPONENT_NOT_REGISTERED
							SimpleMessageBox("COM Exception", "A COM exception occured when unregistering the component with GDS." + e.ToString(), false);
					}

					// remove the program from Run
					RegistryKey key = Registry.CurrentUser.OpenSubKey(kWindowsRunRegKey, true);
					key.DeleteValue(comName, false);
					SimpleMessageBox(comName, "Component unregistered successfully!", false);
					
					//Delete the settings
					RegistryKey appSettings = Registry.CurrentUser.OpenSubKey("Software\\Opera History Component",true); 
					appSettings.DeleteValue("HistoryLocation");
				}
			}
			catch (Exception e) 
			{
				SimpleMessageBox("Exception", "A general error has occured in the registration process.  This is most likely occuring due to a Process Class exception.  Error Details: \n" + e.ToString(), false);
				return false;
			}

			return true;
		}

		
		/// <summary>
		/// Checks whether the history file has been updated since the last index and if it has then it opens it to index
		/// </summary>
		private void ExtractHistory()
		{
			while (true)
			{
				DateTime now = DateTime.Now;
				string file = histFilePath;
				string fileb = bookFilePath;

				try
				{
					if (File.GetLastWriteTime(file).CompareTo(histLastIndexed) > 0)
					{
						ParseAndSendHistory(file);
						histLastIndexed = now;
					}
					if (File.GetLastWriteTime(fileb).CompareTo(bookLastIndexed) > 0)
					{
						ParseAndSendBookmarks(fileb);
						bookLastIndexed = now;
					}
				}
				catch (IOException ioe)
				{
					SimpleMessageBox("Exception", "The history file was not found when OHC tried to access it.  You should edit the registry value at location: HKEY_CURRENT_USER\\Software\\Opera History Component\\HistoryLocation so that it reflects the actual location of your history file. \nThe component will now exit, but when you have made the necessary changes simply run from the menu.", false);	
					Process.GetCurrentProcess().Kill();
				}
				catch (UnauthorizedAccessException uae)
				{
					SimpleMessageBox("Exception", "[EH]You are not an administrator or do not have permission to access this file. - Error Details: " + uae.ToString(), false);
				}
				catch(ArgumentException ae)
				{
					SimpleMessageBox("Exception", "[EH]An error has occured in locating your history file - the path is zero-length. - Error Details: " + ae.ToString(), false);
				}
				catch(Exception e)
				{
					SimpleMessageBox("Exception", "[EH]An unknown exception has occured but the following error details will help in diagnosing the issue. - Error Details: " + e.ToString(), false);
				}
				// Sleep until next needed - 5mins
				Thread.Sleep(60000);
			}

		}

        /// <summary>
        /// Reads in the history file parsing the data and placing it into the correct struct fields.
        /// Makes a call to FetchPageData for the meta description of a page.
        /// </summary>
        /// <param name="histFile">The path to the History file.</param>
		private void ParseAndSendHistory(string histFile)
		{
			StreamReader sr = new StreamReader(histFile);
			string line;
			DateTime convDT = new DateTime(1970, 1, 1, 0, 0, 0, 0);
			double timestamp;
			operaHistoryData ohd = new operaHistoryData();
			
			try
			{
				while((line = sr.ReadLine()) != null)
				{
					ohd.pageTitle = line;
					ohd.pageURL = sr.ReadLine();
					timestamp = Convert.ToDouble(sr.ReadLine());
					ohd.accessTime = convDT.AddSeconds(timestamp);
					
					ohd.pageContent = ohd.pageTitle + " ohcxash";

					line = sr.ReadLine();

					if(ohd.pageURL.IndexOf(":4664") > 0)
					{
						//Do nothing as its either a cached page or a GDS search result page
						//This appears to be done already so I see no need for this but one user requested it.
					}
					else
					{
						SendHistoryData(ohd);
					}
				}
				sr.Close();
			}
			catch(OutOfMemoryException oome)
			{
				SimpleMessageBox("Exception", "[PS]There appears to be a problem allocating memory. - Error Details: " + oome.ToString(), false);
			}
			catch(IOException ioe)
			{
				SimpleMessageBox("Exception", "[PS]An IO exception has occured when reading the file. - Error Details: " + ioe.ToString(), false);
			}
		}

		private void ParseAndSendBookmarks(string bookFile)
		{
			StreamReader srb = new StreamReader(bookFile);
			string line;
			double timestamp;
			DateTime convDT = new DateTime(1970, 1, 1, 0, 0, 0, 0);
			operaBookmarkData obd = new operaBookmarkData();
			
			try
			{			
				while((line = srb.ReadLine()) != null)
				{
					if(line.IndexOf("#URL") >= 0)
					{
						while((line = srb.ReadLine()) != "")
						{
							if(line.IndexOf("NAME") >= 0)
							{
								obd.title = line.Remove(0,line.IndexOf("=")+1);
							}
							else if(line.IndexOf("URL") >= 0)
							{
								obd.url = line.Remove(0,line.IndexOf("=")+1);
							}
							else if(line.IndexOf("VISITED") >= 0)
							{
								timestamp = Convert.ToDouble(line.Remove(0,line.IndexOf("=")+1));
								obd.accessTime = convDT.AddSeconds(timestamp);
							}
						}
						SendBookData(obd);
					}
				}
				srb.Close();
			}
			catch(OutOfMemoryException oome)
			{
				SimpleMessageBox("Exception", "[PSB]There appears to be a problem allocating memory. - Error Details: " + oome.ToString(), false);
			}
			catch(IOException ioe)
			{
				SimpleMessageBox("Exception", "[PSB]An IO exception has occured when reading the file. - Error Details: " + ioe.ToString(), false);
			}
		}

		private void SendHistoryData(operaHistoryData ohd)
		{  
			try
			{
				if(counter == 20)
				{
					Application.Exit();
				}
				GoogleDesktopClass gdsClass = new GoogleDesktopClass();

				object gdsEventDisp = gdsClass.CreateEvent(guid, "Google.Desktop.WebPage");
				IGoogleDesktopEvent gdsEvent = (IGoogleDesktopEvent)gdsEventDisp;

				// Build up properties
				gdsEvent.AddProperty("content", ohd.pageContent);
				gdsEvent.AddProperty("format", "text/html");
				gdsEvent.AddProperty("uri", ohd.pageURL);
				gdsEvent.AddProperty("last_modified_time", ohd.accessTime.ToUniversalTime().ToOADate());
				gdsEvent.AddProperty("title", ohd.pageTitle);
				gdsEvent.AddProperty("author", "Opera");
				gdsEvent.Send(0x01);
				counter++;
			}
			catch (COMException ce)
			{
				// protect some valid error results
				UInt32 error = (UInt32)ce.ErrorCode;
				if (error != 0x80040005 &&    // E_COMPONENT_DISABLED
					error != 0x80040008 &&    // E_EVENT_TOO_LARGE
					error != 0x80040009       // E_SERVICE_NOT_RUNNING
					)
				{
					SimpleMessageBox("COM Exception", "A COM Exception has occured when sending data to GDS.  Ensure GDS is running and not corrupt." + ce.ToString(), false);
				}
			}
			catch(Exception e)
			{
				SimpleMessageBox("Exception", "An unknown exception has occured but the following error details will help in diagnosing the issue. - Error Details: " + e.ToString(), false);
			}
		}

		private void SendBookData(operaBookmarkData obd)
		{  
			try
			{
				if(counter == 20)
				{
					Application.Exit();
				}
				GoogleDesktopClass gdsClass = new GoogleDesktopClass();

				object gdsEventDisp = gdsClass.CreateEvent(guid, "Google.Desktop.WebPage");
				IGoogleDesktopEvent gdsEvent = (IGoogleDesktopEvent)gdsEventDisp;

				// Build up properties
				gdsEvent.AddProperty("content", "Opera Bookmark - ohcbash");
				gdsEvent.AddProperty("format", "text/html");
				gdsEvent.AddProperty("uri", obd.url);
				gdsEvent.AddProperty("last_modified_time", obd.accessTime.ToUniversalTime().ToOADate());
				gdsEvent.AddProperty("title", obd.title);
				gdsEvent.AddProperty("author", "OperaBookmarks");
				gdsEvent.AddProperty("bookmarked", true);
				gdsEvent.Send(0x01);
				counter++;
			}
			catch (COMException ce)
			{
				// protect some valid error results
				UInt32 error = (UInt32)ce.ErrorCode;
				if (error != 0x80040005 &&    // E_COMPONENT_DISABLED
					error != 0x80040008 &&    // E_EVENT_TOO_LARGE
					error != 0x80040009       // E_SERVICE_NOT_RUNNING
					)
				{
					SimpleMessageBox("COM Exception", "A COM Exception has occured when sending data to GDS.  Ensure GDS is running and not corrupt." + ce.ToString(), false);
				}
			}
			catch(Exception e)
			{
				SimpleMessageBox("Exception", "An unknown exception has occured but the following error details will help in diagnosing the issue. - Error Details: " + e.ToString(), false);
			}
		}

		private bool SimpleMessageBox(string caption, string text, bool okCancel) 
		{
			DialogResult result = MessageBox.Show(text, caption, okCancel ? MessageBoxButtons.OKCancel : MessageBoxButtons.OK);
			return (result == DialogResult.OK) ? true : false;
		}
	}
}